#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#define CHAR_DRIVER_TEST_DEBUG
#ifdef CHAR_DRIVER_TEST_DEBUG
    #define test_driver_debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#else
    #define test_driver_debug(fmt, ...)
#endif

struct test_char_drv_dev {
	unsigned long buf_size;          /* buffer 空间大小 */
	unsigned char *buf_data;         /* buffer  */ 

	unsigned int write_index;        /* 待写地址 */
	unsigned int read_index;         /* 待读地址 */
	unsigned char have_writed;
	
	size_t total_read_len;
	size_t total_write_len;

	struct semaphore sem;

	wait_queue_head_t wait_queue; 

	struct kobject *kobj;
};

static int major_id = 100; /* 主设备号 */
static struct test_char_drv_dev char_dev; 
	
static struct attribute test_buf_size_attr = {
	.name = "test_buf_size", 
	.mode = 0644,
};
	
static struct attribute *default_attr[] = {
    &test_buf_size_attr,
    NULL,
};

static void test_buf_size_attr_release(struct kobject *kobj)
{
	//kfree(kobj);
	test_driver_debug("release...\n");
}
 
static ssize_t test_buf_size_attr_show(struct kobject *kobj,
                              struct attribute *attr,
                              char *buf)
{
	struct test_char_drv_dev *dev = &char_dev; 

    test_driver_debug("attr->name=%s size=%ld\n", attr->name, dev->buf_size);

    return sprintf(buf,"%ld", dev->buf_size); 
}
 
static ssize_t test_buf_size_attr_store(struct kobject *kobj,
                                struct attribute *attr,
                                const char *buf, size_t len)
{
	struct test_char_drv_dev *dev = &char_dev; 
	unsigned char *tmp_buf_data;
	size_t tmp_buf_size;
	ssize_t ret;

	ret = kstrtoul(buf, 10, &tmp_buf_size);

	test_driver_debug("tmp_buf_size=%ld buf=%s ret=%ld, len=%ld\n", 
		tmp_buf_size, buf, ret, len);

	if (ret) {
		test_driver_debug("invalid buf_size=%s\n",buf);
		return -EINVAL;
	}

	ret = len;

	/* 限制下最大值 */
	if (tmp_buf_size > 40*1024*1024) {
		test_driver_debug("tmp_buf_size too large size=%ld!\n", tmp_buf_size);
		return -ENOMEM;
	}
	
	/* 先申请   ，申请失败不影响原来使用 */
	tmp_buf_data = vmalloc(tmp_buf_size);
	if (!tmp_buf_size) {
		test_driver_debug("vmalloc size=%ld error!\n", tmp_buf_size);
		return -1;
	}
	memset(tmp_buf_data, 0, tmp_buf_size);

	/* 设置变量 */
	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	/* 重新申请内存 */
	char_dev.buf_size = tmp_buf_size;
	char_dev.read_index = 0;
	char_dev.write_index = 0;
	char_dev.total_read_len = 0;
	char_dev.total_write_len = 0;
	
	if(char_dev.buf_data)
		vfree(char_dev.buf_data);
	
	/* 重新设置地址 */
	char_dev.buf_data = tmp_buf_data;

	up(&dev->sem);

	wake_up_interruptible(&dev->wait_queue);

	test_driver_debug("tmp_buf_size=%ld ret=%ld, len=%ld\n", tmp_buf_size, ret, len);

    return ret;

}
								
static struct sysfs_ops test_buf_size_sysfs_ops = {
     .show = test_buf_size_attr_show,
     .store = test_buf_size_attr_store,
};
 
static struct kobj_type test_buf_size_type = {
    .sysfs_ops = &test_buf_size_sysfs_ops,
    .release = test_buf_size_attr_release,
    .default_attrs = default_attr,
};

static int test_char_drv_open(struct inode *inode, struct file *file)
{
    test_driver_debug("open\n");

	file->private_data = &char_dev;
	
    return 0;
}

static ssize_t test_char_drv_read(struct file *file, char __user *buf,
		    size_t nbytes, loff_t *ppos)
{
	size_t count;
	struct test_char_drv_dev *dev = file->private_data;


	test_driver_debug("reading nbytes=%ld, read_index=%d, write_index=%d\n", 
		nbytes, dev->read_index, dev->write_index);

	if (down_interruptible(&dev->sem)) 
		return -ERESTARTSYS;

	while (dev->write_index == dev->read_index
			&& dev->total_read_len == dev->total_write_len) {
		/* 没有数据读取 */
		up(&dev->sem);
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		test_driver_debug("no data read, now sleeping\n");

		/* 等待唤醒 */
		if (wait_event_interruptible(dev->wait_queue, (dev->write_index != dev->read_index)))
			return -ERESTARTSYS;

		test_driver_debug("wake up\n");

		if (down_interruptible(&dev->sem)) 
			return -ERESTARTSYS;
	}

	/* 处理正常情况 */
	if (dev->write_index > dev->read_index) {
		nbytes = min(nbytes, (size_t)(dev->write_index - dev->read_index));	
		if (copy_to_user(buf, dev->buf_data + dev->read_index, nbytes)) {
			up(&dev->sem);
			return -EFAULT;
		}

		/* 设置读下标 */
		dev->read_index += nbytes;
	}
	else { /* 处理回头情况 */
		count = min(nbytes, (size_t)(dev->buf_size - dev->read_index));
		if (copy_to_user(buf, dev->buf_data + dev->read_index, count)) {
			up(&dev->sem);
			return -EFAULT;
		}

		dev->read_index += count;

		/* 读到了末尾 */
		if (dev->read_index >= dev->buf_size)
			dev->read_index = 0;

		/* 仍然需要读 */
		if (nbytes > count) {
			nbytes = min(nbytes - count, (size_t)(dev->write_index - dev->read_index));	
			if (copy_to_user(buf, dev->buf_data + dev->read_index, nbytes)) {
				up(&dev->sem);
				return -EFAULT;
			}
			dev->read_index += nbytes;
			count += nbytes;
			nbytes = count;
		}
	}

	/* 读到了末尾 */
	if (dev->read_index >= dev->buf_size)
		dev->read_index = 0;

	dev->total_read_len += nbytes;

	up(&dev->sem);

	test_driver_debug("readed nbytes=%ld, read_index=%d, write_index=%d\n", 
		nbytes, dev->read_index, dev->write_index);

	wake_up_interruptible(&dev->wait_queue);
		
	return nbytes;
}

static ssize_t test_char_drv_write(struct file *file, const char __user *buf,
						 size_t nbytes, loff_t *ppos)
{
	size_t count;
	size_t total_write_count = 0;
	
	struct test_char_drv_dev *dev = file->private_data;
			
	test_driver_debug("writing nbytes=%ld, read_index=%d, write_index=%d\n", 
		nbytes, dev->read_index, dev->write_index);

	if (down_interruptible(&dev->sem)) 
		return -ERESTARTSYS;

copy_loop:
	while (dev->write_index == dev->read_index 
			&& dev->total_read_len != dev->total_write_len) {
		up(&dev->sem);
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		test_driver_debug("no space write, now sleeping\n");

		/* 等待唤醒 */
		if (wait_event_interruptible(dev->wait_queue, (dev->write_index != dev->read_index)))
			return -ERESTARTSYS;

		test_driver_debug("wake up\n");

		if (down_interruptible(&dev->sem)) 
			return -ERESTARTSYS;
	}
			
	if (dev->write_index >= dev->read_index) {
		count = min(nbytes, (size_t)(dev->buf_size - dev->write_index));
	}
	else {
		count = min(nbytes, (size_t)(dev->read_index - dev->write_index));
	}
	if (copy_from_user(dev->buf_data + dev->write_index, buf + total_write_count, count)) {
		up(&dev->sem);
		return -EFAULT;
	}
	nbytes -= count;
	dev->write_index += count;
	dev->total_write_len += count;
	total_write_count += count;

	if (dev->write_index >= dev->buf_size) {
		dev->write_index = 0;
	}

	if(nbytes > 0)
		goto copy_loop;
	
	up(&dev->sem);

	test_driver_debug("writed nbytes=%ld, read_index=%d, write_index=%d\n", 
		nbytes, dev->read_index, dev->write_index);
	
	wake_up_interruptible(&dev->wait_queue);

	return total_write_count;
}


static struct file_operations test_char_drv_fops = {
    .open = test_char_drv_open,
    .read = test_char_drv_read,
    .write = test_char_drv_write,
};
	

int test_char_drv_init(void)
{
	int err;
	
	test_driver_debug("int begin...\n");

	memset(&char_dev, 0, sizeof(char_dev));
	char_dev.buf_size = 4*1024*1024;
	char_dev.read_index = 0;
	char_dev.write_index = 0;
	char_dev.total_read_len = 0;
	char_dev.total_write_len = 0;

	char_dev.kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL);
	if (!char_dev.kobj) {
		test_driver_debug("kzalloc kobj error!\n");
		return -1;
	}
	
	sema_init(&char_dev.sem, 1);
	
	/* 先申请内存 */
	char_dev.buf_data = vmalloc(char_dev.buf_size);
	if (!char_dev.buf_data) {
		test_driver_debug("vmalloc buf_data size=%ld error!\n", char_dev.buf_size);
		return -1;
	}
	/* 设置默认值 */
	memset(char_dev.buf_data, 0, char_dev.buf_size);
	
	err = kobject_init_and_add(char_dev.kobj, &test_buf_size_type, 
		  		   &((THIS_MODULE)->mkobj.kobj), "security");
	if (err) {
		kobject_put(char_dev.kobj);
		goto out_free;;
	}
		
    /* 注册字符设备 */
    if (register_chrdev(major_id, "test_char_drv_01", &test_char_drv_fops)) {
		test_driver_debug("unable to get major %d!", major_id);
		goto out_free;
    }	

	init_waitqueue_head(&(char_dev.wait_queue));

	test_driver_debug("init success...\n");

    return 0;

out_free:
	if(char_dev.buf_data) 
		vfree(char_dev.buf_data);

	if(char_dev.kobj)
		kobject_put(char_dev.kobj);
	
	return -1;
}

void test_char_drv_exit(void)
{
	test_driver_debug("exit begin...\n");
	
	if(char_dev.buf_data) 
		vfree(char_dev.buf_data);

	if(char_dev.kobj)
		kobject_del(char_dev.kobj);

    unregister_chrdev(major_id, "test_char_drv_01");

	test_driver_debug("exit success...\n");
}

module_init(test_char_drv_init);
module_exit(test_char_drv_exit);
MODULE_AUTHOR("yankun");
MODULE_LICENSE("GPL");

